package org.hidroinformatica.shapes;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.geotools.data.FeatureSource;
import org.geotools.data.FileDataStore;
import org.geotools.data.FileDataStoreFinder;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.feature.FeatureCollection;
import org.hidroinformatica.shapes.exceptions.EmptyDefinitionMapException;
import org.hidroinformatica.shapes.exceptions.FeatureException;
import org.hidroinformatica.shapes.feature.Definition;
import org.hidroinformatica.shapes.feature.FeatureReader;
import org.opengis.feature.simple.SimpleFeature;

/**
 * Classe principal para a leitura dos shapefiles. Utilize essa classe diretamente.
 * 
 * @author Carlos A. Junior (CIH - Centro Internacional de Hidroinformática)
 */
@SuppressWarnings("rawtypes")
public class ShapeFileReader
{
	private Class targetClass;
	
	private File shapeFile;
	
	private Definition definition;
	
	private SimpleFeatureIterator simpleFeatureIterator;
	
	private int srid = 4326;

	/**
	 * Construtor padrão da classe que recebe um objeto Definition.
	 * 
	 * @param targetClass
	 * 
	 * @param shapeFile
	 * 
	 * @param definition
	 * 
	 * @throws IOException
	 * 
	 * @throws NullPointerException
	 */
	public ShapeFileReader(Class targetClass, File shapeFile, Definition definition) throws IOException, NullPointerException
	{
		super();
		this.targetClass 	= targetClass;
		this.shapeFile 		= shapeFile;
		this.definition 	= definition;
		
		this.init();
	}
	
	/**
	 * Sobrecarga do construtor para receber um objeto Map com as definições de leitura do shapefile.
	 * 
	 * @param targetClass - classe alvo da leitura. Ex.: Cliente.class
	 * 
	 * @param shapeFile - referência para o arquivo .shp
	 * 
	 * @param definicoes - um HashMap de parâmetros.
	 * 
	 * @throws IOException
	 * 
	 * @throws NullPointerException
	 * 
	 * @throws EmptyDefinitionMapException
	 */
	public ShapeFileReader(Class targetClass, File shapeFile, Map<String, String> definicoes) throws IOException, NullPointerException, EmptyDefinitionMapException
	{
		super();
		this.targetClass 	= targetClass;
		this.shapeFile 		= shapeFile;
		
		if(definicoes == null)
		{
			throw new EmptyDefinitionMapException("Nenhuma definição informada.");
		}
		this.definition 	= new Definition();
		
		this.definition.setDefinicoes(definicoes);
		
		this.init();
	}
	
	/**
	 * Inicializa todas as configurações da classe.
	 * 
	 * @throws IOException
	 * 
	 * @throws NullPointerException
	 */
	private void init () throws IOException, NullPointerException
	{
		if(this.targetClass == null)
		{
			throw new NullPointerException("A classe de saída está nula. [" + this.targetClass + "]");
		}
		
		if(this.shapeFile == null || !this.shapeFile.exists())
		{
			throw new IOException("Shape file está nulo ou não foi encontrado. [" + this.shapeFile + "]");
		}
		
		FileDataStore store 				= FileDataStoreFinder.getDataStore(this.shapeFile);
		FeatureSource<?, ?> source 			= store.getFeatureSource();
		FeatureCollection<?, ?> collection 	= source.getFeatures();
		
		this.simpleFeatureIterator 		= (SimpleFeatureIterator) collection.features();
	}
	
	/**
	 * Retorna um objeto List com os objetos populados da classe passada no construtor
	 * da classe.
	 * 
	 * @return List - um ArrayList vazio ou não com os objetos da classe referenciada.
	 */
	public List<Serializable> getRecords ()
	{
		List<Serializable> result = new ArrayList<Serializable>();
		
		FeatureReader reader = new FeatureReader(this.getDefinition());
		
		while (this.getSimpleFeatureIterator().hasNext())
		{
			SimpleFeature feature = (SimpleFeature) this.getSimpleFeatureIterator().next();
			
			try
			{
				Serializable object = reader.getObject(feature, this.getTargetClass(), this.getSrid());
				result.add(object);
			} catch (FeatureException e) {
				e.printStackTrace();
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		
		return result;
	}

	public Class getTargetClass() {
		return targetClass;
	}

	public void setTargetClass(Class targetClass) {
		this.targetClass = targetClass;
	}

	public File getShapeFile() {
		return shapeFile;
	}

	public void setShapeFile(File shapeFile) {
		this.shapeFile = shapeFile;
	}

	public Definition getDefinition() {
		return definition;
	}

	public void setDefinition(Definition definition) {
		this.definition = definition;
	}

	public SimpleFeatureIterator getSimpleFeatureIterator() {
		return simpleFeatureIterator;
	}

	public void setSimpleFeatureIterator(SimpleFeatureIterator simpleFeatureIterator) {
		this.simpleFeatureIterator = simpleFeatureIterator;
	}

	public int getSrid() {
		return srid;
	}

	public void setSrid(int srid) {
		this.srid = srid;
	}
}
